/******************************************************************************
	This file is a part of the Snapshot Bioinformatics Project

	Copyright (C) 2012-2013 Jarom Schow

	This program is free software: you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation, either version 3 of the License, or
	(at your option) any later version.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with this program.  If not, see <http://www.gnu.org/licenses/>.
******************************************************************************/

#include "SeqRecord.h"

#include <QtCore/QVariant>

#include "SeqFeature.h"
#include "SeqReference.h"
#include "Sequence.h"
#include "SqlServer.h"
#include "Strings.h"

using namespace Bio;

class SeqRecord::SeqRecordData : public QSharedData {
public:
	SeqRecordData();
	SeqRecordData( int bioentryId, SqlServer *server );
	~SeqRecordData();

	QString	getId() const;
	void	setId( const QString &id );

	QString	getName() const;
	void	setName( const QString &name );

	QString	getDescription() const;
	void	setDescription( const QString &description );

	QString	getComment() const;
	void	setComment( const QString &comment );

	int		getBioentryId() const;
	void	setBioentryId( int id );

	int		getTaxonId() const;
	void	setTaxonId( int id );

	int		getNcbiTaxonId() const;
	void	setNcbiTaxonId( int id );

	QDate	getDate() const;
	void	setDate( const QDate &date );

	QString	getAnnotation( const QString &key ) const;
	void	setAnnotation( const QString &key, const QString &value );

	QMapIterator<QString, QString>	getAnnotationIterator() const;

	int			getNumFeatures() const;
	SeqFeature*	getFeature( int which ) const;
	void		addFeature( SeqFeature *feature );

	QListIterator<SeqFeature*>	getFeatureIterator() const;

	int				getNumReferences() const;
	SeqReference*	getReference( int which ) const;
	void			addReference( SeqReference *reference );

	QListIterator<SeqReference*>	getReferenceIterator() const;

	int		getNumXRefs() const;
	QString	getXRef( int which ) const;
	void	addXRef( const QString &ref );

	QStringListIterator	getXRefIterator() const;

	Sequence*	getSequence() const;
	void		setSequence( Sequence *seq );

private:

	enum DataFlags {
		NoneLoaded =		0x0000,
		IdLoaded =			0x0001,
		NameLoaded =		0x0002,
		DescriptionLoaded =	0x0004,
		CommentLoaded =		0x0008,
		TaxonIdLoaded =		0x0010,
		NcbiTaxonIdLoaded =	0x0020,
		DateLoaded =		0x0040,
		AnnotationsLoaded =	0x0080,
		FeaturesLoaded =	0x0100,
		ReferencesLoaded =	0x0200,
		XRefsLoaded =		0x0400,
		SequenceLoaded =	0x0800,
		AllLoaded =			0x0fff
	};

	void	loadBaseData();
	void	loadComment();
	void	loadTaxonData();
	void	loadAnnotations();
	void	loadFeatures();
	void	loadReferences();
	void	loadXRefs();
	void	loadSequence();
	void	loadQualifierValues( SeqFeature *feature );
	void	loadDbXRefs( SeqFeature *feature );
	void	loadLocations( SeqFeature *feature );

	int						m_entryId, m_taxonId, m_ncbiId;
	unsigned int			m_flags;
	SqlServer				*m_server;
	QString					m_id, m_name, m_description, m_comment;
	QDate					m_date;
	Sequence				*m_seq;
	QMap<QString, QString>	m_annotations;
	QList<SeqFeature*>		m_features;
	QList<SeqReference*>	m_references;
	QStringList				m_xrefs;
};

/**
**/
SeqRecord::SeqRecord() : m_data( new SeqRecordData )
{
}

/**
**/
SeqRecord::SeqRecord( const SeqRecord &other ) : m_data( other.m_data )
{
}

/**
**/
SeqRecord::SeqRecord( int bioentryId, SqlServer *server ) : m_data( new SeqRecordData( bioentryId, server ) )
{
}

/**
**/
SeqRecord::~SeqRecord()
{
}

/**
**/
QString SeqRecord::getId() const
{
	return m_data->getId();
}

/**
**/
void SeqRecord::setId( const QString &id )
{
	m_data->setId( id );
}

/**
**/
QString SeqRecord::getName() const
{
	return m_data->getName();
}

/**
**/
void SeqRecord::setName( const QString &name )
{
	m_data->setName( name );
}

/**
**/
QString SeqRecord::getDescription() const
{
	return m_data->getDescription();
}

/**
**/
void SeqRecord::setDescription( const QString &description )
{
	m_data->setDescription( description );
}

/**
**/
QString SeqRecord::getComment() const
{
	return m_data->getComment();
}

/**
**/
void SeqRecord::setComment( const QString &comment )
{
	m_data->setComment( comment );
}

/**
**/
int SeqRecord::getBioentryId() const
{
	return m_data->getBioentryId();
}

/**
**/
void SeqRecord::setBioentryId( int id )
{
	m_data->setBioentryId( id );
}

/**
**/
int SeqRecord::getTaxonId() const
{
	return m_data->getTaxonId();
}

/**
**/
void SeqRecord::setTaxonId( int id )
{
	m_data->setTaxonId( id );
}

/**
**/
int SeqRecord::getNcbiTaxonId() const
{
	return m_data->getNcbiTaxonId();
}

/**
**/
void SeqRecord::setNcbiTaxonId( int id )
{
	m_data->setNcbiTaxonId( id );
}

/**
**/
QDate SeqRecord::getDate() const
{
	return m_data->getDate();
}

/**
**/
void SeqRecord::setDate( const QDate &date )
{
	m_data->setDate( date );
}

/**
**/
QString SeqRecord::getAnnotation( const QString &key ) const
{
	return m_data->getAnnotation( key );
}

/**
**/
void SeqRecord::setAnnotation( const QString &key, const QString &value )
{
	m_data->setAnnotation( key, value );
}

/**
**/
QMapIterator<QString, QString> SeqRecord::getAnnotationIterator() const
{
	return m_data->getAnnotationIterator();
}

/**
**/
int SeqRecord::getNumFeatures() const
{
	return m_data->getNumFeatures();
}

/**
**/
SeqFeature* SeqRecord::getFeature( int which ) const
{
	return m_data->getFeature( which );
}

/**
**/
void SeqRecord::addFeature( SeqFeature *feature )
{
	m_data->addFeature( feature );
}

/**
**/
QListIterator<SeqFeature*> SeqRecord::getFeatureIterator() const
{
	return m_data->getFeatureIterator();
}

/**
**/
int SeqRecord::getNumReferences() const
{
	return m_data->getNumReferences();
}

/**
**/
SeqReference* SeqRecord::getReference( int which ) const
{
	return m_data->getReference( which );
}

/**
**/
void SeqRecord::addReference( SeqReference *reference )
{
	m_data->addReference( reference );
}

/**
**/
QListIterator<SeqReference*> SeqRecord::getReferenceIterator() const
{
	return m_data->getReferenceIterator();
}

/**
**/
int SeqRecord::getNumXRefs() const
{
	return m_data->getNumXRefs();
}

/**
**/
QString SeqRecord::getXRef( int which ) const
{
	return m_data->getXRef( which );
}

/**
**/
void SeqRecord::addXRef( const QString &ref )
{
	m_data->addXRef( ref );
}

/**
**/
QStringListIterator SeqRecord::getXRefIterator() const
{
	return m_data->getXRefIterator();
}

/**
**/
Sequence* SeqRecord::getSequence() const
{
	return m_data->getSequence();
}

/**
**/
void SeqRecord::setSequence( Sequence *seq )
{
	m_data->setSequence( seq );
}

/**
**/
SeqRecord& SeqRecord::operator=( const SeqRecord &other )
{
	m_data = other.m_data;

	return *this;
}

/**
**/
SeqRecord::SeqRecordData::SeqRecordData() : m_seq( NULL ), m_entryId( -1 ), m_taxonId( -1 ), m_ncbiId( -1 ),
	m_server( NULL ), m_flags( NoneLoaded )
{
}

/**
**/
SeqRecord::SeqRecordData::SeqRecordData( int bioentryId, SqlServer *server ) : m_seq( NULL ), m_entryId( bioentryId ),
	m_taxonId( -1 ), m_ncbiId( -1 ), m_server( server ), m_flags( NoneLoaded )
{
}

/**
**/
SeqRecord::SeqRecordData::~SeqRecordData() 
{
	delete m_seq;
	qDeleteAll( m_features );
	qDeleteAll( m_references );
}

/**
**/
QString SeqRecord::SeqRecordData::getId() const
{
	if( (m_flags & IdLoaded) == 0 )
		const_cast<SeqRecordData*>( this )->loadBaseData();

	return m_id;
}

/**
**/
void SeqRecord::SeqRecordData::setId( const QString &id )
{
	m_id = id;
	m_flags |= IdLoaded;
}

/**
**/
QString SeqRecord::SeqRecordData::getName() const
{
	if( (m_flags & NameLoaded) == 0 )
		const_cast<SeqRecordData*>( this )->loadBaseData();

	return m_name;
}

/**
**/
void SeqRecord::SeqRecordData::setName( const QString &name )
{
	m_name = name;
	m_flags |= NameLoaded;
}

/**
**/
QString SeqRecord::SeqRecordData::getDescription() const
{
	if( (m_flags & DescriptionLoaded) == 0 )
		const_cast<SeqRecordData*>( this )->loadBaseData();

	return m_description;
}

/**
**/
void SeqRecord::SeqRecordData::setDescription( const QString &description )
{
	m_description = description;
	m_flags |= DescriptionLoaded;
}

/**
**/
QString SeqRecord::SeqRecordData::getComment() const
{
	if( (m_flags & CommentLoaded) == 0 )
		const_cast<SeqRecordData*>( this )->loadComment();

	return m_comment;
}

/**
**/
void SeqRecord::SeqRecordData::setComment( const QString &comment )
{
	m_comment = comment;
	m_flags |= CommentLoaded;
}

/**
**/
int SeqRecord::SeqRecordData::getBioentryId() const
{
	return m_entryId;
}

/**
**/
void SeqRecord::SeqRecordData::setBioentryId( int id )
{
	m_entryId = id;
}

/**
**/
int SeqRecord::SeqRecordData::getTaxonId() const
{
	if( (m_flags & TaxonIdLoaded) == 0 )
		const_cast<SeqRecordData*>( this )->loadBaseData();

	return m_taxonId;
}

/**
**/
void SeqRecord::SeqRecordData::setTaxonId( int id )
{
	m_taxonId = id;
	m_flags |= TaxonIdLoaded;
}

/**
**/
int SeqRecord::SeqRecordData::getNcbiTaxonId() const
{
	if( (m_flags & NcbiTaxonIdLoaded) == 0 )
		const_cast<SeqRecordData*>( this )->loadTaxonData();

	return m_ncbiId;
}

/**
**/
void SeqRecord::SeqRecordData::setNcbiTaxonId( int id )
{
	m_ncbiId = id;
	m_flags |= NcbiTaxonIdLoaded;
}

/**
**/
QDate SeqRecord::SeqRecordData::getDate() const
{
	if( (m_flags & DateLoaded) == 0 )
		const_cast<SeqRecordData*>( this )->loadAnnotations();

	return m_date;
}

/**
**/
void SeqRecord::SeqRecordData::setDate( const QDate &date )
{
	m_date = date;
	m_flags |= DateLoaded;
}

/**
**/
QString SeqRecord::SeqRecordData::getAnnotation( const QString &key ) const
{
	if( (m_flags & AnnotationsLoaded) == 0 )
		const_cast<SeqRecordData*>( this )->loadAnnotations();

	return m_annotations.value( key );
}

/**
**/
void SeqRecord::SeqRecordData::setAnnotation( const QString &key, const QString &value )
{
	m_annotations[key] = value;
}

/**
**/
QMapIterator<QString, QString> SeqRecord::SeqRecordData::getAnnotationIterator() const
{
	if( (m_flags & AnnotationsLoaded) == 0 )
		const_cast<SeqRecordData*>( this )->loadAnnotations();

	return QMapIterator<QString, QString>( m_annotations );
}

/**
**/
int SeqRecord::SeqRecordData::getNumFeatures() const
{
	if( (m_flags & FeaturesLoaded) == 0 )
		const_cast<SeqRecordData*>( this )->loadFeatures();

	return m_features.count();
}

/**
**/
SeqFeature* SeqRecord::SeqRecordData::getFeature( int which ) const
{
	if( (m_flags & FeaturesLoaded) == 0 )
		const_cast<SeqRecordData*>( this )->loadFeatures();

	if( which < 0 || which >= m_features.count() )
		return NULL;

	return m_features[which];
}

/**
**/
void SeqRecord::SeqRecordData::addFeature( SeqFeature *feature )
{
	if( feature )
		m_features.append( feature );
}

/**
**/
QListIterator<SeqFeature*> SeqRecord::SeqRecordData::getFeatureIterator() const
{
	if( (m_flags & FeaturesLoaded) == 0 )
		const_cast<SeqRecordData*>( this )->loadFeatures();

	return QListIterator<SeqFeature*>( m_features );
}

/**
**/
int SeqRecord::SeqRecordData::getNumReferences() const
{
	if( (m_flags & ReferencesLoaded) == 0 )
		const_cast<SeqRecordData*>( this )->loadReferences();

	return m_references.count();
}

/**
**/
SeqReference* SeqRecord::SeqRecordData::getReference( int which ) const
{
	if( (m_flags & ReferencesLoaded) == 0 )
		const_cast<SeqRecordData*>( this )->loadReferences();

	if( which < 0 || which >= m_references.count() )
		return NULL;

	return m_references[which];
}

/**
**/
void SeqRecord::SeqRecordData::addReference( SeqReference *reference )
{
	if( reference )
		m_references.append( reference );
}

/**
**/
QListIterator<SeqReference*> SeqRecord::SeqRecordData::getReferenceIterator() const
{
	if( (m_flags & ReferencesLoaded) == 0 )
		const_cast<SeqRecordData*>( this )->loadReferences();

	return QListIterator<SeqReference*>( m_references );
}

/**
**/
int SeqRecord::SeqRecordData::getNumXRefs() const
{
	if( (m_flags & XRefsLoaded) == 0 )
		const_cast<SeqRecordData*>( this )->loadXRefs();

	return m_xrefs.count();
}

/**
**/
QString SeqRecord::SeqRecordData::getXRef( int which ) const
{
	if( (m_flags & XRefsLoaded) == 0 )
		const_cast<SeqRecordData*>( this )->loadXRefs();

	if( which < 0 || which >= m_xrefs.count() )
		return NULL;

	return m_xrefs[which];
}

/**
**/
void SeqRecord::SeqRecordData::addXRef( const QString &ref )
{
	m_xrefs.append( ref );
}

/**
**/
QStringListIterator SeqRecord::SeqRecordData::getXRefIterator() const
{
	if( (m_flags & XRefsLoaded) == 0 )
		const_cast<SeqRecordData*>( this )->loadXRefs();

	return QStringListIterator( m_xrefs );
}

/**
**/
Sequence* SeqRecord::SeqRecordData::getSequence() const
{
	if( (m_flags & SequenceLoaded) == 0 )
		const_cast<SeqRecordData*>( this )->loadSequence();

	return m_seq;
}

/**
**/
void SeqRecord::SeqRecordData::setSequence( Sequence *seq )
{
	m_seq = seq;
	m_flags |= SequenceLoaded;
}

/**
**/
void SeqRecord::SeqRecordData::loadBaseData()
{
	m_flags |= TaxonIdLoaded | NameLoaded | IdLoaded | DescriptionLoaded;

	if( !m_server )
		return;

	QString sql = "SELECT taxon_id, name, accession, identifier, division, description, "
					"version FROM bioentry WHERE bioentry_id = " + QString::number( m_entryId );

	if( !m_server->exec( sql ) )
		return;

	QSqlQuery	query = m_server->getQuery();

	if( !query.first() )
		return;

	int			version = 0;
	QString		identifier, division;

	m_taxonId = query.value( 0 ).toInt();
	m_name = query.value( 1 ).toString();
	m_id = query.value( 2 ).toString();
	identifier = query.value( 3 ).toString();
	division = query.value( 4 ).toString();
	m_description = query.value( 5 ).toString();
	version = query.value( 6 ).toInt();

	if( !identifier.isEmpty() )
		setAnnotation( c_annGenBankId, identifier );

	if( !division.isEmpty() )
		setAnnotation( c_annDataFileDivision, division );

	if( version > 0 )
		setAnnotation( c_annSequenceVersion, QString::number( version ) );
}

/**
**/
void SeqRecord::SeqRecordData::loadComment()
{
	m_flags |= CommentLoaded;

	if( !m_server )
		return;

	QString	sql = "SELECT comment_text FROM comment WHERE bioentry_id = " +
					QString::number( m_entryId ) + " ORDER BY rank";

	if( !m_server->exec( sql ) )
		return;

	QSqlQuery	query = m_server->getQuery();

	if( !query.first() )
		return;

	m_comment = query.value( 0 ).toString();
}

/**
**/
void SeqRecord::SeqRecordData::loadTaxonData()
{
	m_flags |= NcbiTaxonIdLoaded;

	if( !m_server )
		return;

	int	taxonId = getTaxonId(); // make sure the local id is loaded first

	QString	sql = "SELECT ncbi_taxon_id FROM taxon WHERE taxon_id = " +
					QString::number( taxonId );

	if( !m_server->exec( sql ) )
		return;

	QSqlQuery	query = m_server->getQuery();

	if( !query.first() )
		return;

	m_ncbiId = query.value( 0 ).toInt();
}

/**
**/
void SeqRecord::SeqRecordData::loadAnnotations()
{
	m_flags |= AnnotationsLoaded | DateLoaded;

	if( !m_server )
		return;

	QString	sql = "SELECT name, value FROM bioentry_qualifier_value JOIN term USING (term_id) "
					"WHERE bioentry_id = " + QString::number( m_entryId ) + " ORDER BY rank";

	if( !m_server->exec( sql ) )
		return;

	QSqlQuery	query = m_server->getQuery();

	if( !query.first() )
		return;

	do {
		QString	key = query.value( 0 ).toString();
		QString	value = query.value( 1 ).toString();

		if( key == c_termDateChanged ){
			// set date
		}
		else
			setAnnotation( key, value );
	} while( query.next() );
}

/**
**/
void SeqRecord::SeqRecordData::loadFeatures()
{
	m_flags |= FeaturesLoaded;

	if( !m_server )
		return;

	QString	sql = "SELECT seqfeature_id, type.name, rank FROM seqfeature join term "
					"type on (type_term_id = type.term_id) WHERE bioentry_id = " +
					QString::number( m_entryId ) + " ORDER BY rank";

	if( !m_server->exec( sql ) )
		return;

	QSqlQuery	query = m_server->getQuery();

	if( !query.first() )
		return;

	do {
		int		featureId = query.value( 0 ).toInt();
		QString	type = query.value( 1 ).toString();
		//int		rank = query.value( 2 ).toInt();

		SeqFeature	*feature = new SeqFeature( type );

		feature->setSeqFeatureId( featureId );

		loadQualifierValues( feature );
		loadDbXRefs( feature );
		loadLocations( feature );

		addFeature( feature );
	} while( query.next() );
}

/**
**/
void SeqRecord::SeqRecordData::loadReferences()
{
	m_flags |= ReferencesLoaded;

	if( !m_server )
		return;

	QString	sql = "SELECT start_pos, end_pos, location, title, authors, dbname, accession "
					"FROM bioentry_reference JOIN reference USING (reference_id) "
					"LEFT JOIN dbxref USING (dbxref_id) WHERE bioentry_id = " +
					QString::number( m_entryId ) + " ORDER BY rank";

	if( !m_server->exec( sql ) )
		return;

	QSqlQuery	query = m_server->getQuery();

	if( !query.first() )
		return;

	do {
		int		startPos = query.value( 0 ).toInt();
		int		endPos = query.value( 1 ).toInt();
		QString	location = query.value( 2 ).toString();
		QString	title = query.value( 3 ).toString();
		QString	authors = query.value( 4 ).toString();
		QString	dbname = query.value( 5 ).toString();
		QString	accession = query.value( 6 ).toString();

		
	} while( query.next() );
}

/**
**/
void SeqRecord::SeqRecordData::loadXRefs()
{
	m_flags |= XRefsLoaded;

	if( !m_server )
		return;

	QString	sql = "SELECT dbname, accession, version FROM bioentry_dbxref join dbxref using (dbxref_id) "
					"WHERE bioentry_id = " + QString::number( m_entryId ) + " ORDER BY rank";
	
	if( !m_server->exec( sql ) )
		return;

	QSqlQuery	query = m_server->getQuery();

	if( !query.first() )
		return;

	do {
		QString	dbname = query.value( 0 ).toString();
		QString	accession = query.value( 1 ).toString();
		int		version = query.value( 2 ).toInt();

		if( version > 0 )
			addXRef( dbname + ':' + accession + '.' + QString::number( version ) );
		else
			addXRef( dbname + ':' + accession );		
	} while( query.next() );
}

/**
**/
void SeqRecord::SeqRecordData::loadSequence()
{
	m_flags |= SequenceLoaded;

	if( !m_server )
		return;

	delete m_seq;
	m_seq = m_server->getSequence( m_entryId );
}

/**
**/
void SeqRecord::SeqRecordData::loadQualifierValues( SeqFeature *feature )
{
	if( !m_server || !feature )
		return;

	QString	sql = "SELECT name, value FROM seqfeature_qualifier_value "
					"join term using (term_id) WHERE seqfeature_id = " +
					QString::number( feature->getSeqFeatureId() ) + " ORDER BY rank";
	
	if( !m_server->exec( sql ) )
		return;

	QSqlQuery	query = m_server->getQuery();

	if( !query.first() )
		return;

	do {
		QString	key = query.value( 0 ).toString();
		QString	value = query.value( 1 ).toString();

		feature->setQualifier( key, value );
	} while( query.next() );
}

/**
**/
void SeqRecord::SeqRecordData::loadDbXRefs( SeqFeature *feature )
{
	if( !m_server || !feature )
		return;

	QString	sql = "SELECT dbxref.dbname, dbxref.accession FROM dbxref "
					"join seqfeature_dbxref using (dbxref_id) WHERE "
					"seqfeature_dbxref.seqfeature_id = " + 
					QString::number( feature->getSeqFeatureId() ) + " ORDER BY rank";

	if( !m_server->exec( sql ) )
		return;

	QSqlQuery	query = m_server->getQuery();

	if( !query.first() )
		return;

	QString	dbname = query.value( 0 ).toString();
	QString	accession = query.value( 1 ).toString();
		
	feature->setQualifier( "db_xref", dbname + ':' + accession );
}

/**
**/
void SeqRecord::SeqRecordData::loadLocations( SeqFeature *feature )
{
	if( !m_server || !feature )
		return;

	QString	sql = "SELECT location_id, start_pos, end_pos, strand FROM location "
					"WHERE seqfeature_id = " + QString::number( feature->getSeqFeatureId() ) + 
					" ORDER BY rank";
	
	if( !m_server->exec( sql ) )
		return;

	QSqlQuery	query = m_server->getQuery();

	if( !query.first() )
		return;

	SeqRange	range;

	do {
		//int		locationId = query.value( 0 ).toInt();
		int		start = query.value( 1 ).toInt();
		int		end = query.value( 2 ).toInt();
		int		posNeg = query.value( 3 ).toInt();

		SeqLocation::Strand	strand = SeqLocation::Unknown;

		if( posNeg > 0 )
			strand = SeqLocation::Positive;
		else if( posNeg < 0 )
			strand = SeqLocation::Negative;

		range.addLocation( SeqLocation( start, end, strand ) );
	} while( query.next() );

	feature->setLocation( range );
}
